﻿using System;
using System.Diagnostics;

namespace SfsTools.Core
{
    /// <summary>
    /// Manages free and used index area entries.
    /// </summary>
    internal class IndexAreaEntryRegionMap : RegionMap
    {
        #region Methods

        /// <summary>
        /// Invoked when no blocks could be allocated.
        /// </summary>
        /// <param name="count">The count of blocks requested.</param>
        /// <returns>The <see cref="BlockRegion"/> specifying the region allocated.</returns>
        [DebuggerStepThrough]
        protected override BlockRegion OnFull(long count)
        {
            throw new IndexAreaFullException();
        }

        /// <summary>
        /// Invoked when a region is split after recycling <paramref name="entry"/>.
        /// </summary>
        /// <param name="entry">The entry that was recycled.</param>
        /// <param name="region">The new region that still waits to be recycled.</param>
        /// <returns>An entry representing <paramref name="region"/>.</returns>
        [DebuggerStepThrough]
        protected override RegionMap.RecycleEntry AfterRecycleSplit(RegionMap.RecycleEntry entry, BlockRegion region)
        {
            //// Create unused entries for each entry.
            //for (int i = 0; i < (int)region.Length; i++)
            //{
            //    int entryIndex = (int)region.Start + i;
            //    IndexEntry entry = FEntries[entryIndex];
            //    if (entry != null)
            //    {
            //        FEntries[entryIndex] = entry = new UnusedIndexEntry(FPosition + entryIndex * 64, entry.GetBuffer());
            //        entry.WriteTo(FVolume);
            //    }
            //}
            throw new NotImplementedException();
        }

        /// <summary>
        /// Allocates <paramref name="count"/> free entries.
        /// </summary>
        /// <param name="count">The count of entries to allocate.</param>
        /// <returns></returns>
        /// <exception cref="IndexAreaFullException">The index area is full.</exception>
        [DebuggerStepThrough]
        public int FindAndAllocateEntries(int count)
        {
            return (int)FindAndAllocate(count);
        }

        /// <summary>
        /// Registers a region of index area entries for recycling.
        /// </summary>
        /// <param name="region">The region.</param>
        /// <param name="callback">The callback.</param>
        [DebuggerStepThrough]
        public void Recycle(BlockRegion region, IEntryRecycleCallback callback)
        {
            RegisterRecycleEntry(new RecycleEntry(region, callback));
        }

        /// <summary>
        /// Compacts the index area.
        /// </summary>
        /// <param name="callback">The entry relocation callback.</param>
        public void Compact(IEntryMoveCallback callback)
        {
            //// Clear recycle entries.
            //for (int i = 0; i < FRecycleEntries.Count; i++)
            //{
            //    RecycleEntry re = FRecycleEntries[i];
            //    re.Callback.OnRecycled();
            //    // TODO: mark as free space.
            //}
            //FRecycleEntries.Clear();
        }

        #endregion

        #region Constructors
        /// <overloads>Initializes a new instance of <see cref="IndexAreaEntryRegionMap"/>.</overloads>
        /// <summary>
        /// Initializes a new instance of <see cref="IndexAreaEntryRegionMap"/>.
        /// </summary>
        /// <param name="array">The array containing the bits.</param>
        /// <param name="length">The count of valid bits in <paramref name="array"/>.</param>
        [DebuggerStepThrough]
        public IndexAreaEntryRegionMap(uint[] array, int length)
            : base()
        {
            // Scan list.
            System.Collections.Generic.List<BlockRegion> list = new System.Collections.Generic.List<BlockRegion>();
            bool lastFree = true;
            int lastCount = 0, lastStart = 0;
            for (int index = 0; index < length; index++)
            {
                int idx = index / 32;
                int bit = index % 32;
                bool isFree = (array[idx] & (uint)(1 << bit)) > 0;
                if (lastFree != isFree)
                {
                    // Previous was free, this isnt.
                    list.Add(new BlockRegion(lastStart, lastCount));
                    lastStart = index; lastCount = 1;
                    lastFree = isFree;
                }
                else
                {
                    // No change.
                    lastCount++;
                }
            }
            if (lastCount > 0)
            {
                list.Add(new BlockRegion(lastStart, lastCount));
            }
            // Convert to pairs.
            Debug.Assert((list.Count % 2) == 0);
            System.Collections.Generic.List<BlockRegionPair> pairs = new System.Collections.Generic.List<BlockRegionPair>();
            int i;
            for (i = 0; i < list.Count; i += 2)
            {
                BlockRegion a = list[i + 0];
                BlockRegion b = list[i + 1];
                pairs.Add(new BlockRegionPair(a, b));
            }
            if (i < list.Count)
            {
                // Unpaired.
                BlockRegion region = list[list.Count - 1];
                pairs.Add(new BlockRegionPair(region, new BlockRegion(region.Start + region.Length, 0)));
            }
            SetMap(pairs);
        }
        #endregion

        #region Helper classes

        /// <summary>
        /// Contains a <see cref="BlockRegion"/> and a <see cref="IEntryRecycleCallback"/>.
        /// </summary>
        public new class RecycleEntry : RegionMap.RecycleEntry
        {
            #region Properties

            /// <summary>
            /// Gets the callback to invoke when the region is recycled.
            /// </summary>
            public readonly IEntryRecycleCallback Callback;

            #endregion

            #region Methods

            /// <summary>
            /// Returns a <see cref="String"/> that represents the current <see cref="RecycleEntry"/>.
            /// </summary>
            /// <returns>A <see cref="String"/> that represents the current <see cref="RecycleEntry"/>.</returns>
            [DebuggerStepThrough]
            public override String ToString()
            {
                return new System.Text.StringBuilder().
                    Append('{').Append(Region).Append(' ').Append(Callback.ToString()).Append('}').
                    ToString();
            }

            /// <summary>
            /// Invokes the callback,
            /// </summary>
            [DebuggerStepThrough]
            public override void Recycle()
            {
                Callback.OnRecycled();
            }

            #endregion

            #region Constructors
            /// <overloads>Initializes a new instance of <see cref="RecycleEntry"/>.</overloads>
            /// <summary>
            /// Initializes a new instance of <see cref="RecycleEntry"/>.
            /// </summary>
            /// <param name="region">The block region to recycle.</param>
            /// <param name="callback">The callback to invoke.</param>
            [DebuggerStepThrough]
            public RecycleEntry(BlockRegion region, IEntryRecycleCallback callback)
                : base(region)
            {
                Callback = callback;
            }
            #endregion
        }

        #endregion
    }
}
